FREERTOS的认识1

裸机和rots的认识: 

        裸机:所有的任务放在一个大循环中,轮流调用,实时性差。 

        rtos:每一个任务都是一个循环。通过时间调度的方式进行, 高优先级的可以打断低优先级的任务,中断可以打断一切任务,并通过任务的栈空间回到打断任务的地方继续执行。

最重要的是: 对于延时函数的处理,裸机是一直在等待,而RTOS的操作是让低优先级的执行,时间到后返回继续执行。

FREERTOS 的简介:免费的 ,移植性强,任务没有限制的操作系统,支持抢占式,协程式, 时间片流转任务的调度。后面细琐。

        要求: 学习任务调度,任务启动流程,中断管理。任务切换。列表,队列,信号量,内存管理。

RTOS的简介:

        任务调度简介和任务状态:

        (1): 抢占式调度: 类似中断的优先级的处理,高优先级的打断低优先级的中断,但是数字使越大优先级越高,针对优先级不同的任务的调度

            

        (2):时间片的调度: 每一个Systick的时钟周期来更新一个任务, 针对优先级相同的任务的调度。

        RTOS的四种状态:

        (1)运行状态:每次只能有一个任务处于运行态。

        (2)就绪状态:已经能够执行,但还未执行。

        (3)阻塞状态:因延时和外部中断状态引起。

        (4)挂起状态:类似暂停。

当解除挂起状态后回到就绪状态,并不是运行状态。

FreeRTOS的移植: 具体方法可以参考野火,正点原子的资料的“FREERTOS的开发手册”。

        正点原子的移植后视频上可以运行,但是实际上有些朋友可能出现编译无错误,但是就是不执行响应的操作。 需将其FreeConfig文件的一些的宏定义做出响应的修改:具体可以参考一下代码(做出相应的修改):

#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H



#include "sys.h"
#include "usart.h"

/*-----------------------------------------------------------
 * Application specific definitions.
 *
 * These definitions should be adjusted for your particular hardware and
 * application requirements.
 *
 * THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE
 * FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE.
 *
 * See http://www.freertos.org/a00110.html
 *----------------------------------------------------------*/

/* Ensure stdint is only used by the compiler, and not the assembler. */
#if defined(__ICCARM__)||defined(__CC_ARM)||defined(__GNUC__)
	#include <stdint.h>
	extern uint32_t SystemCoreClock;
#endif


//断言
#define vAssertCalled(char,int) printf("Error:%s,%d\r\n",char,int)
#define configASSERT(x) if((x)==0) vAssertCalled(__FILE__,__LINE__)

/***************************************************************************************************************/
/*                                        FreeRTOS基础配置配置选项                                              */
/***************************************************************************************************************/
#define configUSE_PREEMPTION					1                       //1使用抢占式内核,0使用协程
#define configUSE_TIME_SLICING					1						//1使能时间片调度(默认式使能的)
#define configUSE_PORT_OPTIMISED_TASK_SELECTION	1                       //1启用特殊方法来选择下一个要运行的任务
                                                                        //一般是硬件计算前导零指令,如果所使用的
                                                                        //MCU没有这些硬件指令的话此宏应该设置为0!
#define configUSE_TICKLESS_IDLE					0                       //1启用低功耗tickless模式
#define configUSE_QUEUE_SETS					1                       //为1时启用队列
#define configCPU_CLOCK_HZ						(SystemCoreClock)       //CPU频率
#define configTICK_RATE_HZ						(1000)                  //时钟节拍频率,这里设置为1000,周期就是1ms
#define configMAX_PRIORITIES					(32)                    //可使用的最大优先级
#define configMINIMAL_STACK_SIZE				((unsigned short)130)   //空闲任务使用的堆栈大小
#define configMAX_TASK_NAME_LEN					(16)                    //任务名字字符串长度

#define configUSE_16_BIT_TICKS					0                       //系统节拍计数器变量数据类型,
                                                                        //1表示为16位无符号整形,0表示为32位无符号整形
#define configIDLE_SHOULD_YIELD					1                       //为1时空闲任务放弃CPU使用权给其他同优先级的用户任务
#define configUSE_TASK_NOTIFICATIONS            1                       //为1时开启任务通知功能,默认开启
#define configUSE_MUTEXES						1                       //为1时使用互斥信号量
#define configQUEUE_REGISTRY_SIZE				8                       //不为0时表示启用队列记录,具体的值是可以
                                                                        //记录的队列和信号量最大数目。
#define configCHECK_FOR_STACK_OVERFLOW			0                       //大于0时启用堆栈溢出检测功能,如果使用此功能
                                                                        //用户必须提供一个栈溢出钩子函数,如果使用的话
                                                                        //此值可以为1或者2,因为有两种栈溢出检测方法。
#define configUSE_RECURSIVE_MUTEXES				1                       //为1时使用递归互斥信号量
#define configUSE_MALLOC_FAILED_HOOK			0                       //1使用内存申请失败钩子函数
#define configUSE_APPLICATION_TASK_TAG			0                       
#define configUSE_COUNTING_SEMAPHORES			1                       //为1时使用计数信号量

/***************************************************************************************************************/
/*                                FreeRTOS与内存申请有关配置选项                                                */
/***************************************************************************************************************/
#define configSUPPORT_DYNAMIC_ALLOCATION        1                       //支持动态内存申请
#define configTOTAL_HEAP_SIZE					((size_t)(20*1024))     //系统所有总的堆大小

/***************************************************************************************************************/
/*                                FreeRTOS与钩子函数有关的配置选项                                              */
/***************************************************************************************************************/
#define configUSE_IDLE_HOOK						0                       //1,使用空闲钩子;0,不使用
#define configUSE_TICK_HOOK						0                       //1,使用时间片钩子;0,不使用

/***************************************************************************************************************/
/*                                FreeRTOS与运行时间和任务状态收集有关的配置选项                                 */
/***************************************************************************************************************/
#define configGENERATE_RUN_TIME_STATS	        0                       //为1时启用运行时间统计功能
#define configUSE_TRACE_FACILITY				1                       //为1启用可视化跟踪调试
#define configUSE_STATS_FORMATTING_FUNCTIONS	1                       //与宏configUSE_TRACE_FACILITY同时为1时会编译下面3个函数
                                                                        //prvWriteNameToBuffer(),vTaskList(),
                                                                        //vTaskGetRunTimeStats()
                                                                        
/***************************************************************************************************************/
/*                                FreeRTOS与协程有关的配置选项                                                  */
/***************************************************************************************************************/
#define configUSE_CO_ROUTINES 			        0                       //为1时启用协程,启用协程以后必须添加文件croutine.c
#define configMAX_CO_ROUTINE_PRIORITIES         ( 2 )                   //协程的有效优先级数目

/***************************************************************************************************************/
/*                                FreeRTOS与软件定时器有关的配置选项                                            */
/***************************************************************************************************************/
#define configUSE_TIMERS				        1                               //为1时启用软件定时器
#define configTIMER_TASK_PRIORITY		        (configMAX_PRIORITIES-1)        //软件定时器优先级
#define configTIMER_QUEUE_LENGTH		        5                               //软件定时器队列长度
#define configTIMER_TASK_STACK_DEPTH	        (configMINIMAL_STACK_SIZE*2)    //软件定时器任务堆栈大小

/***************************************************************************************************************/
/*                                FreeRTOS可选函数配置选项                                                      */
/***************************************************************************************************************/
#define INCLUDE_xTaskGetSchedulerState          1                       
#define INCLUDE_vTaskPrioritySet		        1
#define INCLUDE_uxTaskPriorityGet		        1
#define INCLUDE_vTaskDelete				        1
#define INCLUDE_vTaskCleanUpResources	        1
#define INCLUDE_vTaskSuspend			        1
#define INCLUDE_vTaskDelayUntil			        1
#define INCLUDE_vTaskDelay				        1
#define INCLUDE_eTaskGetState			        1
#define INCLUDE_xTimerPendFunctionCall	        1

/***************************************************************************************************************/
/*                                FreeRTOS与中断有关的配置选项                                                  */
/***************************************************************************************************************/
#ifdef __NVIC_PRIO_BITS
	#define configPRIO_BITS       		__NVIC_PRIO_BITS
#else
	#define configPRIO_BITS       		4                  
#endif

#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY			15                      //中断最低优先级
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY	5                       //系统可管理的最高中断优先级
#define configKERNEL_INTERRUPT_PRIORITY 		( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 	( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

/***************************************************************************************************************/
/*                                FreeRTOS与中断服务函数有关的配置选项                                          */
/***************************************************************************************************************/
#define xPortPendSVHandler 	PendSV_Handler
#define vPortSVCHandler 	SVC_Handler

#endif /* FREERTOS_CONFIG_H */


当按下F7进行编译的时候可能会报错(类似与中断优先级的问题):

通过CRTL+F搜索“ __NVIC_PRIO_BITS”找到其相应的宏定义位置将4U换为4即可,

FreeRTOS的任务的创建和删除:

       1.函数的熟悉

        xTaskCreate()                                /*动态的任务的创建(内存是由FreeRTOS自动管理)*/

        xTaskCreateStatic()                       /*静态的任务的创建(需要用户自己申请内存(麻烦))*/

       xTaskDelete()                                 /*任务的删除(通过调用任务的句柄)*/ 

        

      (1).BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,             /*指向任务的函数的指针*/
                            const char * const pcName,                                        /*任务的名字*/
                            const configSTACK_DEPTH_TYPE usStackDepth,   /*任务的堆栈深度*/
                            void * const pvParameters,                                         /*任务的参数*/
                            UBaseType_t uxPriority,                                             /*任务的优先级*/
                            TaskHandle_t * const pxCreatedTask )                      /*任务的句柄(也就是任务控制块)*/

                任务控制块(TCB)就是指的是任务的信息(任务的栈顶指针,状态列表信息,事件列表的状态,优先级,任务栈的起始地址,名字)  类似人的身份证

typedef struct tskTaskControlBlock       
{
    volatile StackType_t * pxTopOfStack;
    /*在任务的切换的时候任务的上下的保存,任务的恢复息息相关*/
    ListItem_t xStateListItem;                  
    /*任务的状态列表项(就绪,运行,挂起,阻塞)*/
    ListItem_t xEventListItem;              
    UBaseType_t uxPriority;                     
    StackType_t * pxStack;                      /*< Points to the start of the stack. */
    char pcTaskName[ configMAX_TASK_NAME_LEN ]; 

}

        (). TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,         /*指任务函数的指针*/
                                    const char * const pcName,                                         /*任务的名字*/
                                    const uint32_t ulStackDepth,                                       /*任务的堆栈大小*/
                                    void * const pvParameters,                                          /*任务的参数*/
                                    UBaseType_t uxPriority,                                              /*任务的优先级*/
                                    StackType_t * const puxStackBuffer,                          /*任务的堆栈, 一般为数组,由用户分配*/
                                    StaticTask_t * const pxTaskBuffer )                      /*任务控制块的指针,由用户分配*/

任务创建完毕后,处于就绪的状态。

        (3)void vTaskDelete( TaskHandle_t xTaskToDelete )

                当任务句柄为NULL,则删除自身(当前运行的任务)注:通过 xTaskCreate()动态创建的任务,在使用vTaskDelete()删除后,该任务创建时申请的堆栈和内存会在系统的空闲任务中被释放掉。

        2.动态任务的创建(实例分析)

        主函数还是我们熟悉的main函数,但FreeRTOS里的main函数不需要自己设计成死循环,只需要创建任务并开启任务调度,即可使系统持续运行。任务的创建一般都是先创建一个开始任务,然后开始任务再负责创建其它子任务。

        在主函数中       

 xTaskCreate((TaskFunction_t      ) start_task,               /*指向任务函数的指针*/
                (const char*         )"start_task",                             /*任务名字(默认为16)*/
                (uint16_t            )START_STK_SIZE,                   /*任务的堆栈大小:单位为字*/
                (void*               )NULL,                                          /*传递给任务函数的参数*/
                (UBaseType_t         )START_TASK_PRIO,          /*任务的优先级*/
                (TaskHandle_t*       )&StartTask_Handler);           /*任务句柄(任务的控制块:删除和添加)*/

        vTaskStartScheduler();           //开启任务的调度函数

        开始任务的函数

//开始任务函数
void start_task(void  *pvParameters)
{ 
    taskENTER_CRITICAL();                    //进入临界区(关闭中断:就是那些不想被打断的程序段)
    
        /*该函数的用于先退出中断,任务创建完毕后在开始从高优先级到低优先级的执行*/  
    xTaskCreate((TaskFunction_t      ) led0_task,
                (const char*         )"led0_task",
                (uint16_t            )LED0_STK_SIZE,
                (void*               )NULL,
                (UBaseType_t         )LED0_TASK_PRIO,
                (TaskHandle_t*       )&LED0Task_Handler);
                    
    xTaskCreate((TaskFunction_t      ) led1_task,
                (const char*         )"led1_task",
                (uint16_t            )LED1_STK_SIZE,
                (void*               )NULL,
                (UBaseType_t         )LED1_TASK_PRIO,
                (TaskHandle_t*       )&LED1Task_Handler);
                
    xTaskCreate((TaskFunction_t      ) float_task,
                (const char*         )"float_task",
                (uint16_t            )FLOAT_STK_SIZE,
                (void*               )NULL,
                (UBaseType_t         )FLOAT_TASK_PRIO,
                (TaskHandle_t*       )&FLOATTask_Handler);
                
    vTaskDelete(StartTask_Handler);         //删除开始任务
                
    taskEXIT_CRITICAL();                    //退出临界区
}

        开始创建子任务的函数

void led0_task(void *pvParameters)
{
    while(1)
    {
        HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_1); 
        vTaskDelay(500);        /* 每个任务函数都是一个死循环,必须加xTaskDelay()函数,用于任务的切换*/
    }
}
void led1_task(void *pvParameters)
{
    while(1)
    {
        printf("aaaaaaa\r\n");
       vTaskDelay(500); 
    }
}
void float_task(void  *pvParameters)
{
    static float float_num = 0.00;
    while(1)
    {
        float_num+=0.01f; 
        printf("float_num的值为%.4f\r\n",float_num); 
        vTaskDelay(1000);
    }
}

FreeRTOS任务的挂起和删除

        (1)vTaskSuspend(LED1Task_Handler);                   //任务的挂起函数

        (2)vTaskResume(LED1Task_Handler);                    //取消的任务的挂起函数

        (3)vTaskResumeFromLSR(LED1Task_Handler);    //在中断中恢复挂起函数

(1)vTaskSuspend(LED1Task_Handler);                   //任务的挂起函数

        要先配置#define INCLUDE_vTaskSuspend                    1 ,通过传递任务句柄来决定。当传输的句柄为NULL,则挂起当前正在运行的任务。

        注意: 挂起并不代表删除,要和删除区分开来

(2)vTaskResume(LED1Task_Handler);                    //取消的任务的挂起函数

        要先配置#define INCLUDE_vTaskSuspend                    1 ,通过传递任务句柄来决定。无论任务的句柄被挂起多少次,只需要解挂一次就可以。

(3)vTaskResumeFromLSR(LED1Task_Handler);    //在中断中恢复挂起函数

        要先配置INCLUDE_xTaskResumeFromLSR和INCLUDE_vTaskSuspend设置为1.中断服务程序中要调用FreeRTOS的API的中断优先级不能高于FreeRTOS所管理的最高优先级。

FreeRTOS的列表和列表项

         列表被FreeRTOS调度器使用,用于跟踪任务,处于就绪、挂起、延时的任务,都会被挂接到各自的列表中。用户程序如果有需要,也可以使用列表。

        列表类似于c语言的链表。 列表项相当于节点,FreeRTOS中的列表是一个双向列表项。列表和数组的区别好处在于,列表的数目可以在后期进行修改和添加,但是数组不行。

        列表的结构体:

typedef struct xLIST
{
     listFIRST_LIST_INTEGRITY_CHECK_VALUE                        /*用于检测列表项数据是否完整*/
     configLIST_VOLATILE UBaseType_t uxNumberOfItems;            /*列表中列表项的个数(不包含xListEnd)*/
     ListItem_t * configLIST_VOLATILE pxIndex;                   /*用于遍历列表*/
     MiniListItem_t xListEnd;                                    /*迷你列表项(末尾列表项)*/
     listSECOND_LIST_INTEGRITY_CHECK_VALUE                       /*用于检测列表项数据是否完整*/
}List_t;

        列表项的结构体:(列表项是列表用于存储数据的地方)

struct xLIST_ITEM
{
     listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE           /*用于检测列表项数据是否完整*/
     configLIST_VOLATILE TickType_t xItemValue;           /*列表项值(常按照升序的方式)*/
     struct xLIST_ITEM * configLIST_VOLATILE pxNext;      /*指向列表中下一个列表项*/
     struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;  /*指向列表中上一个列表项*/
     void * pvOwner;                                     /*指向一个任务TCB*/
     void * configLIST_VOLATILE pvContainer;             /*指向包含该列表项的列表 */
     listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE          /*用于检测列表项数据是否完整*/
};
typedef struct xLIST_ITEM ListItem_t;

        迷你列表项:(仅仅用于标记列表末尾和挂载其他插入列表中的列表项)

struct xMINI_LIST_ITEM
{
     listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE           /*用于检测列表项数据是否完整*/
     configLIST_VOLATILE TickType_t xItemValue;
     struct xLIST_ITEM * configLIST_VOLATILE pxNext;
     struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;

列表和列表项相关API:

        (1).vListInitialise( List_t * const pxList )                                    //初始化列表

        (2).vListInitialiseItem( ListItem_t * const pxItem )                    //初始化列表项

        (3).vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )      //列表插入列表项

        (4).uxListRemove(ListItem_t * const pxNewListItem)               //列表删除列表项

        (5).vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )   //列表末尾插入列表项

        

void vListInitialise( List_t * const pxList )
{
     /*列表索引指向列表项末尾*/
     pxList->pxIndex = ( ListItem_t * )&( pxList->xListEnd );                  
     /* 设置为最大可能值 */
     pxList->xListEnd.xItemValue =portMAX_DELAY;
 
     /* 列表项xListEnd的pxNext和pxPrevious指针指向了它自己 */
     pxList->xListEnd.pxNext = (ListItem_t * ) &( pxList->xListEnd );
     pxList->xListEnd.pxPrevious= ( ListItem_t * ) &( pxList->xListEnd );
     /* 初始化时候,列表数目为0*/
     pxList->uxNumberOfItems = ( UBaseType_t) 0U;
 
     /* 设置为已知值,用于检测列表数据是否完整*/
     listSET_LIST_INTEGRITY_CHECK_1_VALUE(pxList );
     listSET_LIST_INTEGRITY_CHECK_2_VALUE(pxList );
}

        列表项的初始比较简单,只要确保列表项不在任何列表中即可。

void vListInitialiseItem( ListItem_t * const pxItem )
{
     pxItem->pvContainer = NULL;
 
     /*设置为已知值,用于检测列表项数据是否完整*/
     listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE(pxItem );
     listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE(pxItem );
}

        将列表项插入到列表中,列表项所在的位置取决于列表项的列表项值(xItemValue)

void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t *pxIterator;
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;
 
         /* 检查列表和列表项数据的完整性,仅当configASSERT()定义时有效。*/
         listTEST_LIST_INTEGRITY( pxList );
         listTEST_LIST_ITEM_INTEGRITY(pxNewListItem );
 
         /*将新的列表项插入到列表,根据xItemValue的值升序插入列表。*/
         if( xValueOfInsertion == portMAX_DELAY)
         {
                   pxIterator =pxList->xListEnd.pxPrevious;
         }
         else
         {
                   for( pxIterator = (ListItem_t * ) &( pxList->xListEnd );pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator =pxIterator->pxNext )
                   {
                            /* 这里为空 */
                   }
         }
 
         pxNewListItem->pxNext =pxIterator->pxNext;
         pxNewListItem->pxNext->pxPrevious= pxNewListItem;
         pxNewListItem->pxPrevious =pxIterator;
         pxIterator->pxNext = pxNewListItem;
 
         pxNewListItem->pvContainer = ( void* ) pxList;
 
         ( pxList->uxNumberOfItems )++;
}

FreeRTOS的启动任务的调度:

        作用: 用于启动任务调度器,任务调度器启动后,FreeRTOS便会开始任务调度。

vTaskStartScheduler()函数主要实现以下功能:

        (1),创建空闲任务, 

        (2),如果使能软件定时器,则创建定时器任务。

        (3),关闭中断,防止调度器开启之前或过程中,受到中断的干扰,会在运行第一个任务的时候打开。

        (4),初始化全局变量, 并将任务调度器的运行标志设置为已经运行

        (5), 初始化任务运行时间统计功能的时基定时器

        (6), 调用函数xPortStartScheduler();        

函数的分析: 调转进入开启调度器任务函数以后: 

#if ( configUSE_TIMERS == 1 )
        {
            if( xReturn == pdPASS )
            {
                xReturn = xTimerCreateTimerTask();                             //创建软件定时器的任务并作出相应的返回值
            } 
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }

    如果使能软件定时器,则创建定时器任务。

        接着,在前面的任务创建成功后,会接收到一个返回值,当接收到pdPASS后则创建成功

         portDISABLE_INTERRUPTS();                          //关闭中断,运行第一个任务的时候打开

接着执行第四步和第五步:  

        xNextTaskUnblockTime = portMAX_DELAY;              
        xSchedulerRunning = pdTRUE;                                     //开启调度器
        xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;           //心跳节拍

portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();                      //统计任务的时间
 

第六步。调用函数xPortStartScheduler();       //用于完成启动任务调度器中与硬件架构相关的配置部分。   

 xPortStartScheduler()  函数主要实现以下功能: 

        (1)检测用户在FreeRTOSConfig.h文件中对中断的相关配置是否有误。

        (2)配置PendSV和Systick的中断优先级为最低

        (3)调用vPortSetupTimerInterrupt()配置Systick

          (4)   初始化临界区嵌套计数为0

        (5)调用函数prvEnableVFP()使能FPU()

          (6)  调用函数prvStartFirstTask()启动第一个任务.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值