UWB-DW1000的TWR测距及代码(五)

UWB测距过程很简单,两个设备(A和B)。设备A先发送,设备B等待接收

  1. 设备A 发送 P 给 设备B,此时读取时间戳,也可以等收到应答再进行读取,发送时间戳和接收时间戳都会保留最新一次,只要没有被覆盖(发送两次,或者接收两次),都是可以随时读取的。
  2. 设备B 收到P 后,发送R 给设备A,此时读取接收时间戳,和发送 时间戳。
  3. 设备A收到 R 后,发送F给设备B,读取发送时间戳。
  4. 设备B收到 F后,读取接收时间戳。
  5. 设备A 把所有时间戳发给设备B。
  6. 设备B收到 设备A的时间戳,再结合自己的时间戳根据DS——TWR算出两者之间的距离。
  7. 设备B算完距离后再打包发给设备A。
注意,因为时间戳是五个字节的,而我们为了方便发送,是只用了四个字节,舍掉高字节。如果前面的时间戳比后面的时间戳大,说明已经过了一个周期,后者需加上一个周期后再进行计算。因为跑完32位计数都需要 15.65 ps x 2^32 = 67.216ms ,所以不用担心会溢出两次的问题,因为一个测距过程也就是几个毫秒内完成,光速传播。

在这里插入图片描述

//首先发送端
static volatile uint8_t stage;
static uint32_t TX_set_delay_times;
static volatile uint64_t TX_poll_times,TX_resp_times,TX_final_times;
static uint8_t Poll_Buff[]={0x15,0x16,0xab,0,0};        //第一次发送无关数据
static uint8_t Resp_Buff[5]={0};        //接收回应的无关数据的buff
static uint8_t First_Buff[]={0x15,0x16,0xae,0,0,0,0,0,0,0,0,0,0};  //发送第一次发送和接收的时间戳
static uint8_t NO_Buff[5]={0};  //接收无关信息
static uint8_t Final_Buff[9]={0x15,0x16,0xbc,0,0,0,0,0,0};  //发送最后一次发送数据的时间戳

static uint8_t Distance_Buff[13]={0};   //接收距离
//static uint8_t Tx_Final_Buff[]={0x15,0x16,0xae,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0,0};  //最后一次发送时间戳信息过去

unsigned char LCD_Buff[10];
//为了减少发送次数,把时间戳放在握手协议里面发了
int main(void)   
{
	  
    uint8 len = 0,i;  
    /* Start with board specific hardware init. */
    peripherals_init();
    printf("hello dwm1000!\r\n");
   // dwt_dumpregisters();
    /* Reset and initialise DW1000.
     * For initialisation, DW1000 clocks must be temporarily set to crystal speed. After initialisation SPI rate can be increased for optimum
     * performance. */
    reset_DW1000(); /* Target specific drive of RSTn line into DW1000 low for a period. */

    spi_set_rate_low();
    if(dwt_initialise(DWT_LOADUCODE) == -1)
    {
        printf("dwm1000 init fail!\r\n");
        OLED_ShowString(0,0,"INIT FAIL");
        while (1)
        {
            STM_EVAL_LEDOn(LED1);
            deca_sleep(100);
            STM_EVAL_LEDOff(LED1);
            deca_sleep(100);
        }
    }
    spi_set_rate_high();

    /* Configure DW1000. See NOTE 6 below. */
    dwt_configure(&config);
    dwt_setleds(2);
    /* Apply default antenna delay value. See NOTE 1 below. */
    dwt_setrxantennadelay(RX_ANT_DLY);
    dwt_settxantennadelay(TX_ANT_DLY);
    OLED_ShowString(0,0,"INIT PASS");
 
    printf("init pass!\r\n");
    deca_sleep(1000);
while(1)
{
	
	dwt_setrxtimeout(8300);    //设置接收超时时间
	dwt_setrxmode(0,0,0);      //设置接收为正常模式
		 
	dwt_writetxdata(sizeof(Poll_Buff), Poll_Buff, 0);
	dwt_writetxfctrl(sizeof(Poll_Buff), 0);
	dwt_starttx(DWT_START_TX_IMMEDIATE|DWT_RESPONSE_EXPECTED);
				
	while (!((status_reg = dwt_read32bitreg(SYS_STATUS_ID)) & (SYS_STATUS_RXFCG | SYS_STATUS_ALL_RX_ERR))) 
	 { }; //等待接收完成
 
 if (status_reg & SYS_STATUS_RXFCG)
{	
dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_RXFCG | SYS_STATUS_TXFRS); //清除发送和接收状态
len = dwt_read32bitreg(RX_FINFO_ID) & RX_FINFO_RXFLEN_MASK; //获取接收长度
 if (len <= sizeof(Resp_Buff))
	{
		dwt_readrxdata(Resp_Buff, len, 0);
	}	
if(Resp_Buff[0]==0x15&&Resp_Buff[1]==0x16)  //判断头
{
	if(Resp_Buff[2] ==0xac) //判断特有标置位。每次发都不一样,可理解为每个设备特定标签号或基站编号
		{
			TX_poll_times = get_tx_timestamp_u64();  //获取发送端第一次发送的时间戳
			TX_resp_times = get_rx_timestamp_u64();  //获取发送端接收应答数据的的时间戳
	
//   TX_set_delay_times = (TX_resp_times + (RESP_RX_TO_FINAL_TX_DLY_UUS * UUS_TO_DWT_TIME)) >> 8;  //获取延时发送时间点
//   dwt_setdelayedtrxtime(TX_set_delay_times); //设置延时发送时间点
	
// TX_final_times = (((uint64_t)(TX_set_delay_times&0xfffffffe))<<8) + TX_ANT_DLY;  //获取发送端最后一次发送的时间戳
					
		final_msg_set_ts(&First_Buff[3], TX_poll_times);
		final_msg_set_ts(&First_Buff[7], TX_resp_times);
					
		dwt_writetxdata(sizeof(First_Buff), First_Buff, 0);
		dwt_writetxfctrl(sizeof(First_Buff), 0);
		dwt_starttx(DWT_START_TX_IMMEDIATE|DWT_RESPONSE_EXPECTED);
		
		while (!((status_reg = dwt_read32bitreg(SYS_STATUS_ID)) & (SYS_STATUS_RXFCG | SYS_STATUS_ALL_RX_ERR))) 
	 { }; //等待接收距离完成
					 
 if (status_reg & SYS_STATUS_RXFCG)
		{
//							
		dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_RXFCG | SYS_STATUS_TXFRS); //清除发送和接收状态	
		len = dwt_read32bitreg(RX_FINFO_ID) & RX_FINFO_RXFLEN_MASK; //获取接收长度
		if (len <= sizeof(NO_Buff))
			{
				dwt_readrxdata(NO_Buff, len, 0);
			}	
		if((NO_Buff[0]==0x15)&&(NO_Buff[1]==0x16))  //判断头
	{
		if(NO_Buff[2] ==0xad) //判断特有标置位。每次发都不一样,可理解为每个设备特定标签号或基站编号
		{						
		 TX_final_times = get_tx_timestamp_u64();						
	   final_msg_set_ts(&Final_Buff[3], TX_final_times); //将时间戳放入buff发送给接收端
									
		dwt_writetxdata(sizeof(Final_Buff), Final_Buff, 0);
	  dwt_writetxfctrl(sizeof(Final_Buff), 0);
		dwt_starttx(DWT_START_TX_IMMEDIATE|DWT_RESPONSE_EXPECTED);					
	 while (!((status_reg = dwt_read32bitreg(SYS_STATUS_ID)) & (SYS_STATUS_RXFCG | SYS_STATUS_ALL_RX_ERR))) 
				{ }; //等待接收距离完成
									 
	 if (status_reg & SYS_STATUS_RXFCG)
		{ 
			dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_RXFCG | SYS_STATUS_TXFRS); //清除发送和接收状态
		  len = dwt_read32bitreg(RX_FINFO_ID) & RX_FINFO_RXFLEN_MASK; //获取接收长度
			if (len <= sizeof(Distance_Buff))
			 {
				dwt_readrxdata(Distance_Buff, len, 0);
			}
		 if((Distance_Buff[0]==0x15)&&(Distance_Buff[1]==0x16))  //判断头
		{
			if(Distance_Buff[2] ==0xdc) //判断特有标置位。每次发都不一样,可理解为每个设备特定标签号或基站编号
		{
			Distance = (double)Distance_Buff[3] +  ((double)Distance_Buff[4])/100;   	//接收距离
			printf("Distance=%lf\r\n",Distance);
			sprintf(LCD_Buff, "Dis:%3.2fm", Distance);										
			OLED_ShowString(0,2,LCD_Buff);
		}
		else 
		{
			printf("接收距离判断特殊位失败\r\n");
		}
									 
	 }
	 else 
		{
			printf("接收距离判断头错误\r\n");
	  }
	  }
	 else
		{
		 printf("TX-F接收失败\r\n");							 
	  }			
	}
	else
	{
	 dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_ALL_RX_ERR); //清除错误标志
	 printf("发送端接收距离信息的特有标准错误\r\n");
	}
	}
	else
	{
			printf("发送端接收距离信息的帧头错误\r\n");
	}
	}
	else
	{
		dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_ALL_RX_ERR); //清除错误标志
	 }							
	}
else
{
 // stage = POLL;  //数据错误,从头开始	
	printf("发送端应答接收特定标签号错误\r\n");
//	for(i=0;i<len;i++)
//		{
//			 printf("buff[%d]=%x\r\n",i,Tx_Resp_Buff[i]);
//			Tx_Resp_Buff[i] =0;
//		}
		}
	}
	else
	{
		printf("发送端应答接收头错误\r\n");
//	for(i=0;i<len;i++)
//		{
//			printf("buff[%d]=%x\r\n",i,Tx_Resp_Buff[i]);
//			Tx_Resp_Buff[i] =0;
//		}
		}	
	}
	else
	{
		dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_ALL_RX_ERR); //清除错误标志
		printf("发送端应答接收数据错误 !\r\n");
//			for(i=0;i<len;i++)
//			{
//			 printf("buff[%d]=%x\r\n",i,Tx_Resp_Buff[i]);
//				Tx_Resp_Buff[i] =0;
//			}
   } 				 
	}													
				
  }
//首先接收端
#define CYCLE (4294967295.0) //一个周期

unsigned char LCD_Buff[10];
static volatile uint8_t stage;

static  uint32_t TX_poll_times,TX_resp_times,TX_final_times;
static  uint32_t RX_poll_times,RX_resp_times,RX_final_times;

static volatile uint64_t RX_poll_times_64,RX_resp_times_64,RX_final_times_64;
	

static uint8_t Poll_Buff[5]={0};                //第一次接收数据的buff
static uint8_t Resp_Buff[5]={0x15,0x16,0xac,0,0};   //回应无关数据给发送端数据的buff
static uint8_t First_Buff[13]={0};  //  接收发送端第一次发送和接收的时间戳
static uint8_t NO_Buff[5]={0x15,0x16,0xad,0,0};     //回应无关信息
static uint8_t Final_Buff[9]={0};     //接收发送端最后一次发送的时间戳

static uint8_t Distance_Buff[13]={0x15,0x16,0xdc,0,0,0,0}; //发送距离

 double Ra,Rb,Da,Db,Distance;

int tof_dtu;

int main(void)
{
    
    uint8 len = 0,i;
    /* Start with board specific hardware init. */
    peripherals_init();
    printf("hello dwm1000!\r\n");
   // dwt_dumpregisters();
    /* Reset and initialise DW1000.
     * For initialisation, DW1000 clocks must be temporarily set to crystal speed. After initialisation SPI rate can be increased for optimum
     * performance. */
    reset_DW1000(); /* Target specific drive of RSTn line into DW1000 low for a period. */

    spi_set_rate_low();
    if(dwt_initialise(DWT_LOADUCODE) == -1)
    {
        printf("dwm1000 init fail!\r\n");
        OLED_ShowString(0,0,"INIT FAIL");
        while (1)
        {
            STM_EVAL_LEDOn(LED1);
            deca_sleep(100);
            STM_EVAL_LEDOff(LED1);
            deca_sleep(100);
        }
    }
    spi_set_rate_high();

    /* Configure DW1000. See NOTE 6 below. */
    dwt_configure(&config);
    dwt_setleds(1);
    /* Apply default antenna delay value. See NOTE 1 below. */
    dwt_setrxantennadelay(RX_ANT_DLY);
    dwt_settxantennadelay(TX_ANT_DLY);
    OLED_ShowString(0,0,"INIT PASS");

    printf("init pass!\r\n");

    while(1)
		{
			
 dwt_setrxtimeout(0);  
 dwt_setrxmode(0,0,0);   
 dwt_rxenable(0);

 while (!((status_reg = dwt_read32bitreg(SYS_STATUS_ID)) & (SYS_STATUS_RXFCG | SYS_STATUS_ALL_RX_ERR)))
	{ };
	
	if (status_reg & SYS_STATUS_RXFCG)
	{
			
	 dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_RXFCG | SYS_STATUS_TXFRS); //清除发送和接收状态

			
		len = dwt_read32bitreg(RX_FINFO_ID) & RX_FINFO_RXFL_MASK_1023;
		
			if (len <= sizeof(Poll_Buff))
			{
					dwt_readrxdata(Poll_Buff, len, 0);
			}
			
			if(Poll_Buff[0]==0x15&&Poll_Buff[1]==0x16)  //判断头
		{
				if(Poll_Buff[2] ==0xab) //判断特有标置位。每次发都不一样,可理解为每个设备特定标签号或基站编号
				{
					RX_poll_times_64 = get_rx_timestamp_u64();  //获取接收端第一次接收数据的的时间戳
					
			 dwt_setrxaftertxdelay(0);  //设置发送接收后打开接收的延时时间
			 dwt_setrxtimeout(6800);    //设置接收超时时间
			 dwt_setrxmode(0,0,0);      //设置接收为正常模式
			
			 dwt_writetxdata(sizeof(Resp_Buff), Resp_Buff, 0);
			 dwt_writetxfctrl(sizeof(Resp_Buff), 0);
			 dwt_starttx(DWT_START_TX_IMMEDIATE|DWT_RESPONSE_EXPECTED);	

			 while (!((status_reg = dwt_read32bitreg(SYS_STATUS_ID)) & (SYS_STATUS_RXFCG | SYS_STATUS_ALL_RX_ERR)))
			{ };
		
		if (status_reg & SYS_STATUS_RXFCG)
		{
				
			dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_RXFCG | SYS_STATUS_TXFRS); //清除发送和接收状态
				
			len = dwt_read32bitreg(RX_FINFO_ID) & RX_FINFO_RXFL_MASK_1023;
		//	printf("len =%d\r\n",len);
				if (len <= sizeof(First_Buff))
				{
						dwt_readrxdata(First_Buff, len, 0);
				}
				
				if(First_Buff[0]==0x15&&First_Buff[1]==0x16)  //判断头
			{
					if(First_Buff[2] ==0xae) //判断特有标置位。每次发都不一样,可理解为每个设备特定标签号或基站编号
	{			
		RX_resp_times_64 = get_tx_timestamp_u64();  //获取接收端第一次发送数据的的时间戳
		RX_final_times_64 = get_rx_timestamp_u64();  //获取接收端最后一次接收的时间戳
						
		final_msg_get_ts(&First_Buff[3], &TX_poll_times);
		final_msg_get_ts(&First_Buff[7], &TX_resp_times);

						
		RX_poll_times = (uint32_t)RX_poll_times_64;
		RX_resp_times = (uint32_t)RX_resp_times_64;		
		RX_final_times = (uint32_t)RX_final_times_64;    //得到接收端的时间戳
						
		dwt_writetxdata(sizeof(NO_Buff), NO_Buff, 0);
		dwt_writetxfctrl(sizeof(NO_Buff), 0);
	   dwt_starttx(DWT_START_TX_IMMEDIATE|DWT_RESPONSE_EXPECTED);	

	 while (!((status_reg = dwt_read32bitreg(SYS_STATUS_ID)) & (SYS_STATUS_RXFCG | SYS_STATUS_ALL_RX_ERR)))
					{ }; 
					 
	if (status_reg & SYS_STATUS_RXFCG)
		{
			dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_RXFCG | SYS_STATUS_TXFRS); //清除发送和接收状态
	 len = dwt_read32bitreg(RX_FINFO_ID) & RX_FINFO_RXFL_MASK_1023;
		//	printf("len =%d\r\n",len);
	 if (len <= sizeof(Final_Buff))
			 {
				dwt_readrxdata(Final_Buff, len, 0);
			}
	 if(Final_Buff[0]==0x15&&Final_Buff[1]==0x16)  //判断头
			{
			if(Final_Buff[2] ==0xbc) //判断特有标置位。每次发都不一样,可理解为每个设备特定标签号或基站编号
			{
			 final_msg_get_ts(&Final_Buff[3], &TX_final_times); //提取发送端的时间戳
						
			// printf("/*****************************************************/\r\n");
						
//						Ra = (double)(TX_resp_times - TX_poll_times);
//						Da = (double)(TX_final_times - TX_resp_times);
//						Rb = (double)(RX_final_times - RX_resp_times);
//						Db = (double)(RX_resp_times - RX_poll_times);
//														
				if(TX_resp_times > TX_poll_times)  //判断是否超过一周期
				{
					Ra = (double)((double)TX_resp_times - (double)TX_poll_times);
				}
				else
				{
						//	 printf("/****************************/\r\n");
				 Ra = (double)((double)TX_resp_times - (double)TX_poll_times);
						//	 printf("Ra=%lf\r\n",Ra);
				 Ra += CYCLE;   //加上周期
							// printf("Ra=%lf\r\n",Ra);
				}
		
				if(TX_final_times > TX_resp_times)
				{
					Da = (double)((double)TX_final_times - (double)TX_resp_times);
				}
				else
				{
							// printf("/****************************/\r\n");
								
				 Da = (double)((double)TX_final_times - (double)TX_resp_times);
						//	 printf("Da=%lf\r\n",Da);
				 Da += CYCLE;
						//	 printf("Da=%lf\r\n",Da);
				}
						
				if(RX_final_times > RX_resp_times)
				{		
				 Rb = (double)((double)RX_final_times - (double)RX_resp_times);
				}
				else
				{
							 //printf("/****************************/\r\n"); 
			  Rb = (double)((double)RX_final_times - (double)RX_resp_times);
							// printf("Rb=%lf\r\n",Rb);
		 	  Rb += CYCLE;
							// printf("Rb=%lf\r\n",Rb);
			 }
						
			if(RX_resp_times > RX_poll_times)  
			{
				Db = (double)(RX_resp_times - RX_poll_times);
			}
			else
			{
					//printf("/****************************/\r\n");
			 Db = (double)((double)RX_resp_times - (double)RX_poll_times);
				//	printf("Db=%lf\r\n",Db);
			Db += CYCLE;
				//	printf("Db=%lf\r\n",Db);
		}					
			Distance =0;
			tof_dtu = (int64)((Ra * Rb - Da * Db) / (Ra + Rb + Da + Db));
			tof = tof_dtu * DWT_TIME_UNITS;
			Distance = tof * SPEED_OF_LIGHT;
					
					//printf("Dis=%f\r\n",Distance);
					
		Distance = Distance - dwt_getrangebias(config.chan,(float)Distance, config.prf);//距离减去矫正系数
						
					
//						printf("Ra =%lf\r\n",Ra);
//						printf("Rb =%lf\r\n",Rb);
//						printf("Da =%lf\r\n",Da);
//						printf("Db =%lf\r\n",Db);
                          													
//						printf("Ra-Db =%lf\r\n",Ra-Db);
//						printf("Rb-Da =%lf\r\n",Rb-Da);
//												
				printf("Dis=%f\r\n",Distance);
				int temp =0;
				temp = (int)(Distance*100);
					//printf("temp=%d\r\n",temp);
				Distance_Buff[3] = temp/100; //取整
				Distance_Buff[4] = temp%100; //取小数   //打包发送距离
					
//					printf("/**************************************************/\r\n");	
//					printf("TX_poll_times =%u\r\n",TX_poll_times);
//					printf("RX_poll_times =%u\r\n",RX_poll_times);
//					printf("RX_resp_times =%u\r\n",RX_resp_times);
//					printf("TX_resp_times =%u\r\n",TX_resp_times);
//					printf("TX_final_times =%u\r\n",TX_final_times);
//					printf("RX_final_times =%u\r\n",RX_final_times);
//								

//					printf("Distance_Buff[3]=%d\r\n",Distance_Buff[3]);
//					printf("Distance_Buff[4]=%d\r\n",Distance_Buff[4]);
				sprintf(LCD_Buff, "Dis:%3.2fm", Distance);
												
				OLED_ShowString(0,2,LCD_Buff);
					
						
				dwt_writetxdata(sizeof(Distance_Buff), Distance_Buff, 0);
				dwt_writetxfctrl(sizeof(Distance_Buff), 0);
			  dwt_starttx(DWT_START_TX_IMMEDIATE);	
			}
			else
			{
				printf("端接收接收最后一次时间戳失败\r\n");
			}
				}
			}
			else
		{
			dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_ALL_RX_ERR);
			printf("接收发送端最后一次时间戳失败\r\n");
		}
	}
		else
			{ 
					//stage = POLL;  //数据错误,从头开始
					
		printf("接收端最后一次接收特定标签号错误\r\n");
//					for(i=0;i<len;i++)
//					{
//						printf("buff[%d]=%x\r\n",i,Rx_Final_Buff[i]);
//						Rx_Final_Buff[i] =0;
//				}
		}
		
	 }
		else
	{ 
			 //stage = POLL;  //数据错误,从头开始
			
		printf("接收端最后一次接收头错误\r\n");
			 
//			for(i=0;i<len;i++)
//			{
//				printf("buff[%d]=%x\r\n",i,Rx_Final_Buff[i]);
//				Rx_Final_Buff[i] =0;
//			}
		 }
		
	}
 else
	{
		dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_ALL_RX_ERR);
			//stage = POLL;
//									
	printf("接收端最后一次接收数据错误\r\n");
//									 
//			for(i=0;i<len;i++)
//			{
//				printf("buff[%d]=%x\r\n",i,Rx_Final_Buff[i]);
//				Rx_Final_Buff[i] =0;
//			}
			
	}							 
				}
	else
		{
			printf("接收端第一次接收特定标签号错误\r\n");
//					for(i=0;i<len;i++)
//					{
//						printf("buff[%d]=%x\r\n",i,Rx_Poll_Buff[i]);
//						Rx_Poll_Buff[i] =0;
//					}
				}
		
  }
	else
	{ 
			printf("接收端第一次接收头错误\r\n");
//			 for(i=0;i<len;i++)
//					{
//						printf("buff[%d]=%x\r\n",i,Rx_Poll_Buff[i]);
//						Rx_Poll_Buff[i] =0;
//					}
		 }	
	}
 else
	{	
	dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_ALL_RX_ERR);
//										stage = POLL;
//									
	printf("接收端第一次接收数据错误\r\n");
//			for(i=0;i<len;i++)
//				{
//					printf("buff[%d]=%x\r\n",i,Rx_Poll_Buff[i]);
//					Rx_Poll_Buff[i] =0;
//				}
	}
	}
}
  • 9
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 18
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值