前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >STM32F103产品级开源项目:iLook.Time设计解读

STM32F103产品级开源项目:iLook.Time设计解读

作者头像
杨源鑫
发布2020-05-21 15:55:51
8710
发布2020-05-21 15:55:51
举报
文章被收录于专栏:嵌入式开发圈嵌入式开发圈

一、iLook项目的历程:

  • 2013年开始设计iTrack+yeelink,由于各种原因,该项目夭折。
  • 2014年年初开始提出面向产品的开源平台:iLook
  • 2015年5月发起iLook.爱路客
  • 2015年8月发起iLook.Time并开源

iLook它大概就长下面这个样子:

二、iLook平台规格及硬件简介:

  • OLED 128X32显示屏幕
  • 采用ARM STM32F103平台
  • 传感器:BMA250(三轴加速度传感器),BMP280(气压温度传感器),HMC5883(地磁传感器)
  • GPS+内置天线
  • 其他硬件:spi flash 8~16Mbytes, ds1302, 700mA高温锂离子电池

三、iLook软件功能相关

看到网上一些iLook的说明书,最后大致判断有以下这几个界面:

接下来我们就来一步步揭开上面所列功能的面纱:

1、主程序框架

分析任何一个项目,都是从main.c的main函数开始,从头到尾把握整个程序的框架,接下来咱们再去了解细节功能,以下是main函数的实现,在这里我顺便再多注释下代码的含义:

代码语言:javascript
复制
int main(void)
{
    uint8_t *logo_ptr;
    int logo_width, logo_height;

    GPIO_Config();
    //开机上电,判断是否低电压,如果是则关机,这一步非常重要,这是产品级必备的功能。
    PowerMonitorTask_Init();
    //判断电压是否小于3.4V,如果是则关机
    if( gPowerSt.BatteryVol < 3.4 )
    {
        LCM_PWR_OFF();
        GPS_PwrOff();
        DEV_PWR_OFF();
        myPWR_EnterPowrOff();
    }
    //ARM初始化
    UART1_Configuration();
    EXTI_Configuration();
    NVIC_Configuration();
    I2C_Config();
    //这个延时主要是让上面的硬件配置稳定
    delay_us(2000);
    //Qst初始化
    SysTick_Init();
    QstMonitor_Init();
    //驱动初始化
    spiflash_init();
    //加载文件系统,并获取系统配置
    disc_mount();
    ilook_cfg_load();   //获取配置文件
    //任务初始化
    RealTime_Init();    //这个必须最先启动
    UsbMonitorTask_Init();
    DisplayTask_Init();
    //开机显示
    QstCtrl(&DisplayTskInfo, DISPLAY_PWR_ON);
    //加载系统LOGO
    if( (logo_ptr = load_logo(&logo_width, &logo_height)) != 0 )
    {
        Glph_DrawBitmap(0, 0, BMP_FILE | BIT_MAP_REVERSE, logo_width, logo_height, logo_ptr);
    }
    else
    {
        Glph_Print(0, 0, MS_GOTHIC_8X16, (char*)prj_info);
        Glph_Print(0, 24, ASCII_5X7, (char*)prj_version);
    }

    //开机显示
    while(TimeOutCheck_Sec(iLookCfg.T_LogoDisplay) == 0)
    {
        //显示LOGO
        DisplayTask();

        //如果两秒内松开按键,则关机
        //检查POWER键是否有效,上面这个两秒内松开则关机处理的非常好,因为产品嘛,存在用户不小心勿触的情况。
        if( TimeOutCheck_Sec(1) == 0 )
        {
            if( GPIO_ReadInputDataBit(WKUP_KEY, WKUP_KEY_PIN) == 0 )
            {
                LCM_PWR_OFF();
                GPS_PwrOff();
                DEV_PWR_OFF();
                myPWR_EnterPowrOff();
            }
        }
    }
    ClrScreen();
    LED_OFF();
    //启动系统
    KeyTask_Init();
    sys_log_write("POWER ON", "OK");
    UiTask_Init();
    while(1)
    {
        //产生计数,以便后面的任务获取执行时间间隔
        QstMonitor();
        //主要是电源管理,读电量以及检测是否为充电模式
        PowerMonitorTask();
        //主要是控制USB状态的切换:打开、关闭、检测是否连接、挂载与解除挂在文件系统
        if( UsbMonitorTask() == 1 )
            continue;
        //实时时钟任务,主要用于实时显示DS1302的时间(年月日,时分秒)
        RealTime_Task();
        //显示任务,主要是处理显示器的电源开关、休眠唤醒、亮度设置的状态切换
        DisplayTask();
        //UI任务处理
        UiTask();
    }
}

2、QST管理状态机任务系统

QST管理状态机是整个工程的核心,接下来我们来了解下QST管理状态机主要在工程代码的task.h和task.c里实现,核心结构体:

代码语言:javascript
复制
typedef struct _TASK_CTRL_INFO
{
    unsigned char Ctrl;       //任务命令输入,第8位必须是1。TASK_CMD|New State
    unsigned char State;      //任务当前状态
    unsigned long TickMsk;    //任务时间戳
    unsigned long TickGap;    //任务时间间隔
    unsigned int  MsgFlg;     //任务新信息标志位
    unsigned char *Msg;       //任务信息指针
    char (*Process)(void);    //任务函数指针
} TASK_CTRL_INFO;

相应的,task.h定义了外部可访问结构体成员的方法以及状态的设置切换:

代码语言:javascript
复制
/* QST进程管理系统定义 */
#define TASK_CMD            0x80      //任务标志
#define TASK_MSG_NULL       0x00      //无信息
#define TASK_MSG_ST_CHANGE  0x80      //状态切换信息

//任务信息处理
void QstMsgClr( TASK_CTRL_INFO *tsk );
unsigned char QstGetMsgState( TASK_CTRL_INFO *tsk );
unsigned char *QstGetMsg( TASK_CTRL_INFO *tsk );

//任务状态机控制
void QstCtrl( TASK_CTRL_INFO *tsk, unsigned char ctrl );

//任务状态机跳转
void QstEnter( TASK_CTRL_INFO *tsk, unsigned char st );

//获取外部控制命令
unsigned char QstGetCmd( TASK_CTRL_INFO *tsk );

//获取任务状态
unsigned char QstGetState( TASK_CTRL_INFO *tsk );

//复位任务计时器
void QstRestTskTick( TASK_CTRL_INFO *tsk );

//QST看守进程
void QstMonitor_Init(void);
void QstMonitor(void);

//任务公有状态定义
#define T_NULL      0x00    //空状态
#define T_PWR_ON    0x01    //任务打开状态
#define T_PWR_OFF   0x70    //任务关闭状态
#define T_HW_ERR    0x71    //任务相关硬件错误状态


/*---------------------------------------------------------------------*/
/* 项目所涉及的TASK声明全部放到这里 */
extern TASK_CTRL_INFO UiTskInfo;        //系统顶层任务

extern TASK_CTRL_INFO CompassTskInfo;   //指南针驱动任务

extern TASK_CTRL_INFO DisplayTskInfo;   //显示驱动任务

extern TASK_CTRL_INFO gSensorTskInfo;   //加速度传感器驱动任务

extern TASK_CTRL_INFO GpsTskInfo;       //GPS驱动任务

extern TASK_CTRL_INFO PowerTskInfo;     //电源管理任务

extern TASK_CTRL_INFO BaroTskInfo;      //气压传感器任务
/*---------------------------------------------------------------------*/

遗憾的是,iLook.Time仅仅开源了代码框架以及部分任务的实现,这里面主要实现了系统顶层任务、显示驱动任务、电源管理任务,剩下的几个在代码里都没有实现,不过这不影响我们继续学习作者的设计思想。

关于task.c代码注释的非常详细,主要是实现了用户可设置和访问的任务的成员的接口,最精华的地方就是每个任务的时间间隔以及时间戳的处理,这部分将是这份代码最重要的地方。

代码语言:javascript
复制
/**
  ******************************************************************************
  * @file    task.c
  * @author  SZQVC
  * @version V1.0.0
  * @date    2015.2.14
  * @brief   灯塔计划.海啸项目 (QQ:49370295)
  *          QST前后台进程管理系统
  ******************************************************************************
  * @attention                                                                 *
  *                                                                            *
  * <h2><center>&copy; COPYRIGHT 2015 SZQVC</center></h2>                      *
  *                                                                            *
  * 文件版权归“深圳权成安视科技有限公司”(简称SZQVC)所有。*
  *                                                                            *
  *        http://www.szqvc.com                                                *
  *                                                                            *
  ******************************************************************************
**/
#include "stm32f10x.h"
#include "sys_tick.h"
#include "task.h"

/* define */
struct _QST_STATE
{
    uint32_t mloop_per_sec;
} Qst;

/* public */

/* extern */

/* private */
static unsigned long mon_task_tick, mon_task_loop_cnt;


/*******************************************************************************
* Function Name  : TaskCtrl
* Description    : 任务状态切换
* Input          : - tsk: 任务结构指针
*                  - ctrl: 切换到什么状态
* Output         : None
* Return         : None
*******************************************************************************/
void QstCtrl(TASK_CTRL_INFO *tsk, unsigned char ctrl)
{
    tsk->Ctrl = ctrl | TASK_CMD;
    tsk->TickMsk = GetSysTick_ms();  //记录进入该状态的时间标签

    //立即执行一次任务
    if( tsk->Process != 0x0 )
        tsk->Process();
}


/*******************************************************************************
* Function Name  : TaskEnter
* Description    : 任务状态跳转
* Input          : - tsk: 任务结构指针
*                  - st: 任务直接进入到什么状态
* Output         : None
* Return         : None
*******************************************************************************/
void QstEnter( TASK_CTRL_INFO *tsk, unsigned char st )
{
    tsk->MsgFlg = TASK_MSG_ST_CHANGE;      //进入新的状态,信息应该被更新
    tsk->State = st;                       //设定状态
    tsk->TickMsk = GetSysTick_ms();        //记录进入该状态的时间标签
}

/*******************************************************************************
* Function Name  : QstRestTaskTick
* Description    : 复位任务计数器
* Input          : - tsk: 任务结构指针
* Output         : None
* Return         : None
*******************************************************************************/
void QstRestTskTick( TASK_CTRL_INFO *tsk )
{
    tsk->TickMsk = GetSysTick_ms();        //记录进入该状态的时间标签
}


/*******************************************************************************
* Function Name  : QstGetCmd
* Description    : 获取任务控制命令
* Input          : - tsk: 任务结构指针
* Output         : T_NULL or Command
* Return         : None
*******************************************************************************/
unsigned char QstGetCmd( TASK_CTRL_INFO *tsk )
{
    if( tsk->Ctrl & TASK_CMD )
    {
        tsk->Ctrl &= ~TASK_CMD;
        return tsk->Ctrl;
    }
    else
        return T_NULL;
}

/*******************************************************************************
* Function Name  : QstGetState
* Description    : 获取任务状态
* Input          : - tsk: 任务结构指针
* Output         : Task state
* Return         : None
*******************************************************************************/
unsigned char QstGetState( TASK_CTRL_INFO *tsk )
{
    return tsk->State;
}

/*******************************************************************************
* Function Name  : QstGetMsg
* Description    : 获取任务信息指针
* Input          : - tsk: 任务结构指针
* Output         : 输出任务信息指针
* Return         : None
*******************************************************************************/
unsigned char *QstGetMsg( TASK_CTRL_INFO *tsk )
{
    return tsk->Msg;
}

/*******************************************************************************
* Function Name  : QstGetMsgState
* Description    : 获取任务信息标志
* Input          : - tsk: 任务结构指针
* Output         : 0 没有信息
* Return         : None
*******************************************************************************/
unsigned char QstGetMsgState( TASK_CTRL_INFO *tsk )
{
    return tsk->MsgFlg;
}

/*******************************************************************************
* Function Name  : QstMsgClr
* Description    : 清除任务信息
* Input          : - tsk: 任务结构指针
* Output         : None
* Return         : None
*******************************************************************************/
void QstMsgClr( TASK_CTRL_INFO *tsk )
{
    tsk->MsgFlg = TASK_MSG_NULL;
}

/*******************************************************************************
* Function Name  : TaskMonitor
* Description    : 主循环每秒执行次数,任务信息监视任务.
* Input          : -
* Output         : None
* Return         : None
*******************************************************************************/
void QstMonitor_Init(void)
{
    mon_task_tick = 0;
}

void QstMonitor(void)
{
    //主循环速度
    if( GetSysTick_ms() > mon_task_tick + 1000 )
    {
        mon_task_tick = GetSysTick_ms();
        Qst.mloop_per_sec = mon_task_loop_cnt;
        mon_task_loop_cnt = 0;
    }
    else
    {
        mon_task_loop_cnt++;
    }

    //
}

/*************************** (C) COPYRIGHT SZQVC ******************************/
/*                              END OF FILE                                   */
/******************************************************************************/

这里GetSysTick_ms其实是获取了系统定时器时钟,作者将系统定时器配置为1ms中断一次,主要实现在sys_tick.h和sys_tich.c中:

sys_tick.h 提供了初始化以及设置/获取系统时钟的相关接口

代码语言:javascript
复制
/***********************************************************************/
/*                         SZQVC.Lighthouse                            */
/*                           www.szqvc.com                             */
/***********************************************************************/

#ifndef __SYS_TICK_H

#define __SYS_TICK_H

void SysTick_Ctrl(uint16_t cmd);
void SysTick_Init(void);

uint32_t GetSysTick_ms(void);
uint32_t GetSysTick_Sec(void);

void MarkSysTick_ms(uint32_t *t);
void MarkSysTick_Sec(uint32_t *t);

char TimeOutCheck_Sec(uint32_t i);
char TimeOutCheck_ms(uint32_t i);

void delay_ms(uint32_t i);
void delay_us(uint32_t i);

#endif


/*********************** (C) COPYRIGHT SZQVC **************************/
/*                          END OF FILE                               */
/**********************************************************************/

sys_tick.c 实现了初始化以及设置/获取系统时钟的相关接口

代码语言:javascript
复制
/**
  ******************************************************************************
  * @file    sys_tick.c
  * @author  SZQVC
  * @version V1.0.0
  * @date    2015.2.14
  * @brief   灯塔计划.海啸项目 (QQ:49370295)
  *          system tick,与CPU相关
  ******************************************************************************
  * @attention                                                                 *
  *                                                                            *
  * <h2><center>&copy; COPYRIGHT 2015 SZQVC</center></h2>                      *
  *                                                                            *
  * 文件版权归“深圳权成安视科技有限公司”(简称SZQVC)所有。*
  *                                                                            *
  *        http://www.szqvc.com                                                *
  *                                                                            *
  ******************************************************************************
**/
#include "stm32f10x.h"
#include "sys_tick.h"

/* define */
struct _SYS_TICK_TYPE
{
    uint32_t ms;
    uint32_t ten_ms;
    uint32_t Sec;
} systick;

#define us        12      //@72MHz


/* public */

/* extern */

/* private */


/*******************************************************************************
* Function Name  : SysTick_Init
* Description    : 系统定时器时钟初始化
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void SysTick_Init(void)
{
    systick.ms = 0;
    systick.Sec = 0;
    SysTick_Config(SystemCoreClock / 1000);  //1ms中断一次
}

/*******************************************************************************
* Function Name  : SysTick_Ctrl
* Description    : 系统定时器时钟ENABLE/DISABLE
* Input          : ENABLE/DISABLE
* Output         : None
* Return         : None
*******************************************************************************/
void SysTick_Ctrl(uint16_t cmd)
{
    if( cmd == ENABLE )
    {
        SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
    }
    else if( cmd == DISABLE)
    {
        SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
    }
}

/*******************************************************************************
* Function Name  : SysTick_Handler
* Description    : 系统定时器中断
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
extern void KeyTask(void);

void SysTick_Handler(void)
{
    systick.ms++;

    if(systick.ms % 1000 == 0)
        systick.Sec++;

    /*需要在定时器中处理的任务 */
    KeyTask();
}

/*******************************************************************************
* Function Name  : SysTick_Handler
* Description    : 获取ms计数器
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
uint32_t GetSysTick_ms(void)
{
    return systick.ms;
}

void MarkSysTick_ms(uint32_t *t)
{
    *t = systick.ms;
}

/*******************************************************************************
* Function Name  : GetSysTick_Sec
* Description    : 获取sec计数器
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
uint32_t GetSysTick_Sec(void)
{
    return systick.Sec;
}

void MarkSysTick_Sec(uint32_t *t)
{
    *t = systick.Sec;
}

/*******************************************************************************
* Function Name  : delay_nus
* Description    : 延时n us
* Input          : i
* Output         : None
* Return         : None
*******************************************************************************/
void delay_us(uint32_t i)
{
    i = i * us;

    while(i--);
}

/*******************************************************************************
* Function Name  : delay_ms
* Description    : 延时n ms
* Input          : i
* Output         : None
* Return         : None
*******************************************************************************/
void delay_ms(uint32_t i)
{
    uint32_t end_t = systick.ms + i;

    while( systick.ms < end_t );
}

/*******************************************************************************
* Function Name  : TimeOutCheck_Sec, TimeOutCheck_ms
* Description    : 延时n ms
* Input          : i
* Output         : None
* Return         : None
*******************************************************************************/
char TimeOutCheck_Sec(uint32_t i)
{
    if( systick.Sec >= i )
        return 1;
    else
        return 0;
}

char TimeOutCheck_ms(uint32_t i)
{
    if( systick.ms >= i )
        return 1;
    else
        return 0;
}

其实,作者的这种方法在我之前公众号里某些文章也有体现,但不得不说,作者在此基础上设计了数据结构,更优雅的去管控这些要执行的任务,可见作者对数据结构、系统定时器的运用以及状态机框架的设计思想非常的精妙绝伦。

3、U盘(用于存储系统参数+其它文件)

U盘主要是基于STM32的USB+fatfs文件系统,存储介质主要是基于SPI_FLASH,明摆了说就是把SPI_FLASH虚拟成一个U盘,然后用来存储配置参数,以及系统日志还有其它的一些信息,主要我们来看下配置参数这块,配置参数使用了一个庞大的结构体进行描述:

代码语言:javascript
复制
typedef struct
{
    //系统配置
    char GPX_onoff;             //GPX记录打开/关闭
    char TimeZone;              //时区
    char GPS_PosConvert;        //坐标转换
    char AltitudeType;          //海拔类型,=0 气压海拔,=1 GPS海拔,=2 综合海拔
    char GpxSaveCnt;            //GPX几个点快速存储
    char WaveType;              //=0海拔, =1温度,=2气压
    tm   tCountDown;            //倒计时器
    //界面加载管理
    char GotoWin_onoff;
    char WeatherWin_onoff;
    char PositionWin_onoff;
    char ShakeCountGame_onoff;
    char DebugWin_onoff;
    char WhenWhereWin_onoff;
    char TimerWin_onoff;
    char NpsWin_onoff;
    char AltitudeTempWin_onoff;
    char TravelWin_onoff;
    //时间设定
    int  T_LogoDisplay;         //LOGO显示时间
    int  T_ScreenAutoCloseTime; //sec,屏幕自动关闭时间
    int  T_GPSSearchTimeMax;    //sec,gps允许搜星最长时间
    int  T_GPSSleepSec_Car;     //在开车状态的GPS间歇开机时间
    int  T_GPSSleepSec_Walk;    //在步行状态的GPS间歇开机时间
    int  T_TravelRestMax;       //旅途最长休息时间
    int  T_WeatherInterval;     //天气采集间隔
    int  T_ScreenCloseLongTime; //一些特殊界面的长延时关屏
    int  T_WeatherWave;         //波形采集密度
    //GSENSOR设定
    unsigned char g_slope_th;
    unsigned char g_slope_dur;
    unsigned char g_ig_incr_step;
    unsigned char g_ig_dec_step;
    int g_ig_wkup_level;
    int g_ig_move_level;
    int g_ig_max_cnt;
    int g_mmt_flt_scale;
    int g_mmt_offset;
    //OLED亮度
    unsigned char oled_contrast;//OLED亮度
    unsigned char oled_fosc;    //OLED频率
    unsigned char flip_onoff;   //OLED反转
    //OTHER
    int gpx_min_distance;

} SYS_CFG_TYPE;

最后参数是存放在CFG_FILE_NAME这个文件里:

代码语言:javascript
复制
#define GPX_PATH        "Gpx"
#define CFG_FILE_NAME   "time.txt"
#define LOG_FILE_NAME   "syslog.txt"
#define GPX_FILE_NAME   "yymmdd.gpx"

那么参数是怎么获取的呢?在最开始的代码里已经有了体现,通过调用ilook_cfg_load函数进行加载,该函数比较长,我们只截取一部分:

代码语言:javascript
复制
void ilook_cfg_load(void)
{
    char tmp_str[100];
    char n[10];
    int i_tmp;
    uint32_t t;

    //系统默认值
    iLookCfg.TimeZone = 8;
    iLookCfg.WaveType = 2;
    //
    iLookCfg.WeatherWin_onoff = 1;
    iLookCfg.DebugWin_onoff = 0;
    iLookCfg.TimerWin_onoff = 1;
    //
    iLookCfg.T_LogoDisplay = 2;
    iLookCfg.T_ScreenAutoCloseTime = 60;
    iLookCfg.T_GPSSearchTimeMax = 90;
    iLookCfg.T_WeatherInterval = 1;
    iLookCfg.T_WeatherWave = 10;
    //
    iLookCfg.g_slope_th = 35;         //0x18-0x03,
    iLookCfg.g_slope_dur = 0;
    iLookCfg.g_ig_incr_step = 20;
    iLookCfg.g_ig_dec_step = 1;
    iLookCfg.g_ig_wkup_level = 40;
    iLookCfg.g_ig_move_level = 300;
    iLookCfg.g_ig_max_cnt = 6000;
    iLookCfg.g_mmt_flt_scale = 5;
    iLookCfg.g_mmt_offset = 15;
    //
    iLookCfg.oled_contrast = 0x7F;    //oled对比度
    iLookCfg.oled_fosc = 0xa0;        //oled显示频率设定
    iLookCfg.flip_onoff = 0;

    //other
    if( f_open(&cfgFIL, CFG_FILE_NAME, FA_READ) == FR_OK )
    {
        while( 1 )
        {
            //读取CFG文件一行
            if( f_gets(tmp_str, 100, &cfgFIL) == NULL )
                break;

            //系统配置
            if( strstr(tmp_str, "GPX_onoff") )
            {
                get_para(tmp_str, n);

                if( isdecstring(n) < 2 )
                {
                    iLookCfg.GPX_onoff = DecStr2Int(n, 1);
                }
            }
            else if( strstr(tmp_str, "TimeZone") )
            {
                get_para(tmp_str, n);

                if( isdecstring(n) < 3 )
                {
                    iLookCfg.TimeZone = DecStr2Int(n, 2);
                }
            }
    ......
       }
  }

在文件系统没有相应的文件的时候,会启用默认的参数进行加载,这样做的好处是确保文件系统加载不起来的时候,还能采用系统默认自带的参数去运行,当加载了文件系统,如果里面找到对应的配置文件,则会把一开始的默认参数覆盖一遍。其余的部分限于篇幅留给读者自行学习分析。

项目资料下载

代码语言:javascript
复制
链接:https://pan.baidu.com/s/12sTRiqJcYgoeW7IkXl2TFw
提取码:c0rr
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 嵌入式云IOT技术圈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、iLook项目的历程:
  • 二、iLook平台规格及硬件简介:
  • 三、iLook软件功能相关
    • 1、主程序框架
      • 2、QST管理状态机任务系统
        • 3、U盘(用于存储系统参数+其它文件)
        • 项目资料下载
        相关产品与服务
        对象存储
        对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档