csr8670--sink工程的大致工作流程分析(以speaker为例)一

今天是14号 15号更新,说明,刚开始看程序有点复杂 没办法,必须看代码
建议首先先把ADK3.5.1中的例程tutorials看完,对学习很有帮助

1.csr中的消息机制

1.1 adk3.5.1中的led的例子

  • csr8670中是以消息机制进行任务调度的,根据消息调用相应的回调函数进行处理,如下面这个例子所示:在主函数中,首先设置PIO为输出并且设置为低电平,然后调用了messagesend函数和messageloop函数
  • TaskData :这个结构体是最重要的,可以看出定义了一个函数指针,其参数有三个
typedef struct TaskData { void (*handler)(Task, MessageId, Message); } TaskData;
  • messagesend:这个函数能够向某个任务发送消息
  • messageloop:开始任务调度,不会返回
static void led_controller1( Task t, MessageId id, Message payload )
{
    PioSet32( LED1, (PioGet32() ^ LED1) );
    MessageSendLater( t, 0, 0, DELAY1 );
}
static void led_controller2( Task t, MessageId id, Message payload )
{
    PioSet32( LED2, (PioGet32() ^ LED2) );
    MessageSendLater( t, 0, 0, DELAY2 );
}
static TaskData led_controller1_task = { led_controller1 };//实例化了一个任务,并初始化了其参数,即回调函数
static TaskData led_controller2_task = { led_controller2 };

int main(void)
{
    PioSetDir32(0xFF, 0xFF);         /* Set all PIO to be output */
    PioSet32(0xFF, 0);               /* Set all PIO off (0) */

    MessageSend( &led_controller1_task, 0 , 0 );//第一个参数为发送给哪个任务
    MessageSend( &led_controller2_task, 0 , 0 );
    MessageLoop();//开始任务调度

    return 0;
}

1.2 hsTaskData结构体

csr中是以消息机制来进行任务的处理的,其中最大的任务为theSink,其结构体如下

/* Sink application data */
typedef struct
{
    /* Main task */
    TaskData                 task;//最重要的任务,指向最高层的回到函数,即main函数里边的app_handler

    /* Config variables */
    ButtonsTaskData          *theButtonsTask;//按键的任务,这些是最高层任务之下的任务,之所以定义在最高层里面,应该是想让最高层具有知道所有事件运行情况的能力
    LedTaskData              *theLEDTask;//led显示的任务,同上
    runtime_block1_t         *rundata;//运行时的数据,目前还不了解
    config_block1_t          *conf1;//解下来的7个block以后会分析
    config_block2_t          *conf2;
    config_block3_t          *conf3;
    config_block4_t          *conf4;
    config_block5_t          *conf5;
    config_block6_t          *conf6;
    config_block7_t          *conf7;

    ConfigTone_t              gConfigTones;

    volume_levels_t          *volume_levels;        /* current operating volume levels for a2dp/usb/wired modes */

    power_table              *user_power_table;     /* pointer to user power table if available in ps */
    hfp_common_plugin_params_t  hfp_plugin_params;
    HFP_features_type        HFP_supp_features;
    feature_config_type      features;
    bdaddr                       local_bd_addr; /* Local BD Address of the sink device available in ps */

    /* Runtime variables */
    /*下面这些运行时的变量都是在动态的生成或者释放的,每一个和button一样都是一个任务*/
    Task                     codec_task ;
    Sink                     routed_audio;
    uint16                   NoOfReconnectionAttempts;
    profile_data_t           profile_data[MAX_MULTIPOINT_CONNECTIONS];
    a2dp_data                *a2dp_link_data;
#ifdef ENABLE_AVRCP
    avrcp_data               *avrcp_link_data;
#endif    
    tp_bdaddr                *confirmation_addr;
    inquiry_data_t           inquiry;

#ifdef ENABLE_PBAP
    pbapc_data_t             pbapc_data;
#endif
#ifdef ENABLE_USB
    usb_info                 *usb;
#endif
#ifdef ENABLE_PEER
    bdaddr remote_peer_ag_bd_addr;
#endif
    user_eq_bank_t        *PEQ;

    /*! Runtime flags*/
    /*word 1*/
    unsigned                 PowerOffIsEnabled:1; /*自行百度结构体的用法,这样写应该是为了节省空间*/
    unsigned                 SinkInitialising:1;
    unsigned                 VolumeOrientationIsInverted:1; /*whether or not the vol buttons are inverted*/
    unsigned                 NetworkIsPresent:1;

    unsigned                 inquiry_scan_enabled:1;
    unsigned                 page_scan_enabled:1 ;
    unsigned                 csr_speech_recognition_is_active:1 ;
    unsigned                 csr_speech_recognition_tuning_active:1 ;         

    unsigned                 panic_reconnect:1;              
    unsigned                 last_outgoing_ag:2 ;   /* which AG made the last outgoing call */
    unsigned                 audio_prompts_enabled:1;

    unsigned                 confirmation:1;
    unsigned                 debug_keys_enabled:1;
    unsigned                 RepeatCallerIDFlag:1;
    unsigned                 mute_all_outputs:1;

    /*word 2*/
    unsigned                 audio_prompt_language:4; 
    unsigned                 num_audio_prompt_languages:4;
    unsigned                 MultipointEnable:1;
    unsigned                 powerup_no_connection:1;    /* bit to indicate device has powered and no connections yet */
    unsigned                 paging_in_progress:1;       /* bit to indicate that device is curretly paging whilst in connectable state */
    unsigned                 audioAmpPowerPin:1;         /* bit to indicate logic state of audio amplifier power pin */

    unsigned                 battery_state:3;
    unsigned                 gVolButtonsInverted:1;    /*! whether or not the volume button operation is currently inverted*/  

    /*word 3*/
    unsigned                 FailAudioNegotiation:1;
    unsigned                 RenegotiateSco:1;       
    unsigned                 lbipmEnable:1;             /* enable Low Battery Intelligent Power Management feature */
    unsigned                 buttons_locked:1;          /* Flag to indicate if button locking is enabled */

    unsigned                 HeldCallIndex:4;           /* which call to route in the case of multiple held calls */ 

    unsigned                 inquiry_tx:8;

    /*word 4*/
    unsigned                 MissedCallIndicated:8;
    unsigned                 gEventQueuedOnConnection:8 ;

    /*word 5*/
    unsigned                 mute_states:3;
    unsigned                 pbap_access:1;             /* Link Policy expedites PBAP access */
    unsigned                 dfu_access:1;              /* Link Policy expedites DFU data transfer */
    unsigned                 hfp_profiles:3;
    unsigned                 ssr_enabled:1;
    unsigned                 VoiceRecognitionIsActive:2;
    unsigned                 PartyModeEnabled:1;
#ifdef ENABLE_PEER    
    unsigned                 remote_peer_audio_conn_status:3; /* Flag to indicate which Audio sources are connected to the remote peer */
    unsigned                 tws_qual_enable_peer_open:1; /* Flag to indicate App to trigger opening of Peer media channel */
#else
    unsigned                 unused4:4;
#endif

    /* word 6 */
    unsigned                 gated_audio:8;              /* Bitmask indicating which audio sources are prevented from being routed */
    unsigned                 linkLossReminderTime:8;

    /* word 7 */
    peer_states_t            peer;
#if defined(ENABLE_PEER) &&  defined( ENABLE_PEER_BATTERY_LEVEL)
    uint16                   peer_battery_level;
#endif
} hsTaskData;

2.sink工程的流程分析

2.1 sink的初始化和工具

  • 如下图所示,描述了之前提到过的csr的开发流程,即pc工具和开发环境配合使用,所以在程序中需要读取这些配置的参数等数据
    csr8670--sink工程的大致工作流程分析(以speaker为例)一_第1张图片

2.2 speaker流程分析

  • 第一步:init函数,对sink工程进行单步调试发现,工程首先进入的入口函数是init函数,而不是main函数,下面都是大致的分析,对整个流程有个初步的认识即可
/* Time critical initialisation */
#ifdef HOSTED_TEST_ENVIRONMENT
void _sink_init(void)
#else
void _init(void)
#endif
{
    /* Set the application task */
    theSink.task.handler = app_handler;  /*这个就是上面提及的最重要的任务的回调函数,如果其他任务想让这个任务的事件触发,则向它发送消息,第一个参数为这个任务的TASK*/

    /* set flag to indicate that configuration is being read, use to prevent use of variables
       prior to completion of initialisation */
    theSink.SinkInitialising = TRUE;/*将sink的正在初始化标志置位*/
    /* Read in any PIOs required */
    configManagerPioMap();/*根据配置文件获取PIO的设置和映射关系*/
    /* Time critical USB setup */
    usbTimeCriticalInit();/*目前没有用到*/
}
  • 第二步:mian函数,看函数注释说sink引用程序从这里开始,突然发现上面的init函数前面包含了一个下划线,这个带下划线的函数我们应该是单步不到的,应该是系统自动上电执行的,超出我们单步调试的范围,应该是系统内部的函数,目前不是很了解。
/* The Sink Application starts here...*/
#ifdef HOSTED_TEST_ENVIRONMENT
int sink_main(void)
#else
int main(void)
#endif
{
    DEBUG (("Main [%s]\n",__TIME__));

    /* Initialise the Upgrade lib */
    sinkUpgradeInit(&theSink.task);/*这个和更新有关,暂不考虑*/

    /* check and update as necessary the software version pskey, this is used for ensuring maximum
       compatibility with the sinkg configuration tool */
    configManagerSetVersionNo();/*版本相关*/

    /* Initialise memory required early */
    configManagerInitMemory();/*这个函数里面开辟了runtime_block1_t         这个结构体,初始化为0*/

    /* initialise memory for the led manager */
    LedManagerMemoryInit();/*开辟了LedTaskData结构体的空间,初始化为0*/

    /* Initialise device state */
    AuthResetConfirmationFlags();/*不懂?*/

    /*the internal regs must be latched on (smps and LDO)*/
    PioSetPowerPin ( TRUE ) ;/*不懂?*/

    switch (BootGetMode() )/*配置工具可以设置系统的启动模式*/
    {
#ifdef CVC_PRODTEST
        case BOOTMODE_CVC_PRODTEST:
            /*run the cvc prod test code and dont start the applicaiton */
            cvcProductionTestEnter() ;
        break ;
#endif
        case BOOTMODE_DFU:
            /*do nothing special for the DFU boot mode,
            This mode expects to have the appropriate host interfface enabled
            Don't start the application */

            /* Initializing only the system components required for flashing the led pattern in the DFU mode*/
            configManagerInit(FALSE);
            LEDManagerIndicateEvent(EventUsrEnterDFUMode);
        break ;

        case BOOTMODE_DEFAULT:
        case BOOTMODE_CUSTOM:
        case BOOTMODE_USB_LOW_POWER:
        case BOOTMODE_ALT_FSTAB:
        default:
        {
            /* Initialise the Connection lib */
            sinkConnectionInit();/*初始化连接库,从这里开始,可以把整个连接库从初始化调用到完成分析一下*/

            #ifdef TEST_HARNESS
                test_init();
            #endif
        }
        break ;
    }

    /* Make sure the mute states are correctly set up */
    VolumeSetInitialMuteState();/*静音模式初始化*/

    /* Start the message scheduler loop */
    MessageLoop();/*开始任务调度*/

    /* Never get here...*/
    return 0;
}
  • 第三步:连接库的具体初始化过程
  • 2.3.1.sinkConnectionInit函数,注意这个函数使用static修饰
static void sinkConnectionInit(void)
{
    /* read the lengths key into a temporary malloc to get pdl length */
    /*给pdl分配空间*/
    lengths_config_type * lengths_key = PanicUnlessMalloc(sizeof(lengths_config_type));

    /* The number of paired devices can be restricted using pskey user 40,  a number between 1 and 8 is allowed */
    /*读取pdl*/
    ConfigRetrieve(CONFIG_LENGTHS, lengths_key , sizeof(lengths_config_type) );
    DEBUG (("PDLSize[%d]\n" , lengths_key->pdl_size ));

    /* Initialise the Connection Library with the options */
    /*具体的初始化函数,注意(0001)第一个参数为最上层的任务,第三个参数为pdl的个数*/
    ConnectionInitEx2(&theSink.task , NULL, lengths_key->pdl_size );

    /* free the malloc'd memory */
    free(lengths_key);/*这里需要关注一下,这种malloc,free的模式在工程中很常见,都是先分配空间,使用之后再释放,在哪里看的目的好像是说减少栈内存的使用*/
}
  • 2.3.2.看源代码可知ConnectionInitEx2–>ConnectionInitEx3,所以接下来是ConnectionInitEx3这个函数,参数前三个对应
void ConnectionInitEx3(Task theAppTask, const msg_filter *msgFilter , uint16 TdlNumberOfDevices  , uint16 options)
{
    /*注意这里面又有了一个theCm,自己可以分析一下,怎样和最重要的任务产生关系*/
    theCm.msgFilter = (msgFilter == NULL) ? &defaultMsgFilter : msgFilter;

    /* Initialise the Connection Library Task, all upstream messages sent by
       Bluestack will be handled by this task */
    /*这也是个任务,初始化其回调函数*/
    theCm.task.handler = connectionBluestackHandler;

    /* If a task is already registered to receive BlueStack prims then we panic! */
    if (MessageBlueStackTask(connectionGetCmTask()))
    {
        CL_DEBUG(("ERROR - task already registered\n"));
    }

    /* Init the resource locks */
    initLocks();

    /* Init the sm_init_msg types.*/
    theCm.smState.sm_init_msg = sm_init_set_none;

    /* Store the application task */
    /*在这一步,将最终要的任务,赋值给这个任务的结构体,这样这个任务就可以向最重要任务发消息*/
    theCm.theAppTask = theAppTask;


    /*set the number of devices to the requested value if in the range 1 to 8*/
    theCm.smState.TdlNumberOfDevices = DEFAULT_NO_DEVICES_TO_MANAGE ;
    if ((TdlNumberOfDevices >= MIN_NO_DEVICES_TO_MANAGE) && 
        (TdlNumberOfDevices <= MAX_NO_DEVICES_TO_MANAGE))
    {
        theCm.smState.TdlNumberOfDevices = TdlNumberOfDevices ;
    }

    /* Process options */

    /* Enable SC */
    if (options & CONNLIB_OPTIONS_SC_ENABLE)
        theCm.flags |= CONNECTION_FLAG_SC_ENABLE;

    /* Enable SC only mode. It implies SC must be turned on as well */
    if (options & CONNLIB_OPTIONS_SCOM_ENABLE)
        theCm.flags |= (CONNECTION_FLAG_SCOM_ENABLE | CONNECTION_FLAG_SC_ENABLE);

    /* Start the initialisation process */
    MessageSend(connectionGetCmTask(), CL_INTERNAL_INIT_REQ, NO_PAYLOAD);/*这里发送消息给&theCm.task,也即上面赋值过的回调函数connectionBluestackHandler,第二个代表连接内部初始化请求此数值为1,第三个参数延时时间*/
}
  • 2.3.3 现在走进connectionBluestackHandler这个函数看怎样处理这个消息
/****************************************************************************
NAME
    connectionBluestackHandler

DESCRIPTION
    This is the main task handler for all messages sent to the Connection
    Library task.

RETURNS
    void
*/
void connectionBluestackHandler(Task task, MessageId id, Message message)
{
    /* Get access to the Connection Library instance state */
    /*获取这个传递过来的任务*/
    connectionState *theCm = (connectionState *)task;
    connectionStates state = theCm->state;

    PRINT(("connectionBluestackHandler - Id = 0x%x\n",id));

    /* Handle Bluestack primitives seperately */
    switch (id)
    {
        case MESSAGE_BLUESTACK_DM_PRIM:/*8004*/
            connectionBluestackHandlerDm(theCm, (DM_UPRIM_T *)message);
            break;

#ifndef CL_EXCLUDE_RFCOMM
        case MESSAGE_BLUESTACK_RFCOMM_PRIM:/*8006*/
            connectionBluestackHandlerRfcomm(theCm, (RFCOMM_UPRIM_T *)message);
            break;
#endif

#if !defined(CL_EXCLUDE_L2CAP) || !defined(DISABLE_BLE)
        case MESSAGE_BLUESTACK_L2CAP_PRIM:/*8005*/
            connectionBluestackHandlerL2cap(theCm, (L2CA_UPRIM_T *)message);
            break;
#endif

#ifndef CL_EXCLUDE_SDP
        case MESSAGE_BLUESTACK_SDP_PRIM:/*8007*/
            connectionBluestackHandlerSdp(theCm, (SDS_UPRIM_T *)message);
            break;
#endif

        case MESSAGE_BLUESTACK_UDP_PRIM:/*8015*/
        case MESSAGE_BLUESTACK_TCP_PRIM:/*8014*/
            handleUnexpected(connectionUnhandledMessage, theCm->state, id);
            break;

#ifndef CL_EXCLUDE_SDP
        /* CL_SDP_CLOSE_SEARCH_CFM Primitive arrived as a result of an internal
           call to close SDP search, can't avoid so ignore
           Handled as a special case to allow the compiler to generate better
           code for the previous switch statements. */
        case CL_SDP_CLOSE_SEARCH_CFM:
            break;
#endif

        /* Everything else must be internal connection library primitives */
        default:/*可知1会进入这里*/
        {
            switch (state)/*然会根据任务的状态判断,我们可以回过头去看看这个任务的状态在什么时间被赋值了,发现没有处理过所以为0,会进入connectionUninitialised这个值也为0*/
            {
                case connectionReady:
                    connectionBluestackHandlerReady(theCm, id, message);
                    break;

                case connectionUninitialised:
                    connectionBluestackHandlerUninitialised(theCm, id, message);/*注意传递过去的参数*/
                    break;

                case connectionInitialising:
                    connectionBluestackHandlerInitialising(theCm, id, message);
                    break;

                case connectionTestMode:
                    connectionBluestackHandlerTestMode(theCm, id, message);
                    break;
            }
        }
    }
}
  • 2.3.4 connectionBluestackHandlerUninitialised函数
static void connectionBluestackHandlerUninitialised(connectionState *theCm, MessageId id, Message message)
{
    /* Depending upon the message id...*/
    /*由前面可以知道此时相等*/
    if (id == CL_INTERNAL_INIT_REQ)
    {
        PRINT(("CL_INTERNAL_INIT_REQ\n"));
        connectionHandleInternalInit(connectionInit);/*执行这个函数,注意参数为0*/
    }
    else
    {
        /* Prims we are not handling - Not panicing the app in DEBUG Mode. Just Print this INFO and ignore it.*/
        CL_DEBUG_INFO(("Ignored Unexpected Message - Code 0x%x State 0x%x MsgId 0x%x\n", connectionUnexpectedCmPrim, theCm->state, id))
    }
}
  • 2.3.5 connectionHandleInternalInit函数
void connectionHandleInternalInit(connectionInitState state)
{
    /* If we're ready to run, change state */    
    if(state == connectionInitComplete)
    {
        theCm.state = connectionReady;
    }
    else if (theCm.state != connectionInitialising)/*这个成立,此时状态为未初始化*/
    {
        theCm.state = connectionInitialising;/*改变状态*/

        /* Start a Timer to notify the Client if the initialisation fails */
        /*等待一段时间发送CL_INTERNAL_INIT_TIMEOUT_IND这个消息此数值为0,还是向connectionBluestackHandler发送的,注意第三个参数,这个是个时间,很重要下面会分析到*/
        MessageSendLater(&theCm.task, CL_INTERNAL_INIT_TIMEOUT_IND, NO_PAYLOAD, (uint32) INIT_TIMEOUT);
    }

    /* Check to see if all objects have been initialised */
    if(state == connectionInitComplete)
    {
        /* Initialise auth requirements to unknown */
        theCm.smState.authentication_requirements = AUTH_REQ_UNKNOWN;

        /* Some DM stuff can be initialised only after the DM register has happened so do it here */
        connectionDmInfoInit();

        /* Let the application we're ready to go */
        connectionSendInitCfm(theCm.theAppTask, success, theCm.infoState.version);
    }
    else
    {
        /* Depending upon the previous object initialised, initialise the next one */
        switch(state)
        {
            case connectionInit:/*此时会调用这个函数*/
                connectionDmInit(); /*这个是个初始化,做什么的不了解*/
                break;

            case connectionInitDm:
#ifndef CL_EXCLUDE_RFCOMM
                connectionRfcInit();    
#else
                connectionL2capInit();
#endif
                break;

#ifndef CL_EXCLUDE_RFCOMM
            case connectionInitRfc:
                connectionL2capInit();
                break;
#endif

            case connectionInitL2cap:
                connectionUdpInit();
                break;

            case connectionInitUdp:
                connectionTcpInit();
                break;

            case connectionInitTcp:
                connectionSdpInit(&theCm.sdpState);
                break;

            case connectionInitSdp: 
                connectionVersionInit();
                break;

            case connectionInitVer:
                connectionSmInit(theCm.infoState.version,
                                 &theCm.smState,
                                 theCm.flags);
                break;

            case connectionInitSm:
                theCm.smState.noDevices = connectionAuthInit();
                break;

            case connectionInitComplete:
                /* We're ready! */                
            default:
                break;
        }
    }
}
  • 2.3.6 接下来第二次进入connectionBluestackHandler这个函数,只不过此时的状态已经更改为theCm.state = connectionInitialising;所以进入的函数为case connectionInitialising:
    connectionBluestackHandlerInitialising(theCm, id, message);
    break;
  • 2.3.7 connectionBluestackHandlerInitialising函数分析,此时的id为CL_INTERNAL_INIT_TIMEOUT_IND
static void connectionBluestackHandlerInitialising(connectionState *theCm, MessageId id, Message message)
{
    /* Depending upon the message id...*/
    switch (id)
    {
        case CL_INTERNAL_INIT_CFM:
            PRINT(("CL_INTERNAL_INIT_CFM\n"));
            connectionHandleInternalInit(((CL_INTERNAL_INIT_CFM_T*)message)->state);
            break;

        case CL_INTERNAL_INIT_TIMEOUT_IND:/*所以会执行下面的语句*/
            PRINT(("CL_INTERNAL_INIT_TIMEOUT_IND\n"));
            (void)MessageCancelFirst(&theCm->task, CL_INTERNAL_INIT_CFM);
            SET_CM_STATE(connectionUninitialised);
            connectionSendInitCfm(theCm->theAppTask, fail, bluetooth_unknown);/*这个函数是个重点,关注它的第一个参数为最重要的那个任务*/
            break;

        case CL_INTERNAL_SM_INIT_REQ:
            PRINT(("CL_INTERNAL_SM_INIT_REQ\n"));
            handleSecurityInitReq(&theCm->infoState, (CL_INTERNAL_SM_INIT_REQ_T *)message);
            break;

        case CL_INTERNAL_DM_READ_LOCAL_VERSION_REQ:
             PRINT(("CL_INTERNAL_DM_READ_LOCAL_VERSION_REQ\n"));
             connectionHandleReadLocalVersionRequest(&theCm->infoState, (CL_INTERNAL_DM_READ_LOCAL_VERSION_REQ_T *)message);
             break;

        case CL_INTERNAL_DM_SET_BT_VERSION_REQ:
            PRINT(("CL_INTERNAL_DM_SET_BT_VERSION_REQ\n"));
            connectionHandleSetBtVersionReq(&theCm->infoState, (CL_INTERNAL_DM_SET_BT_VERSION_REQ_T *)message);
            break;

        default:
            /* Prims we are not handling - for now panic the app */
            handleUnexpected(connectionUnhandledMessage, theCm->state, id);
            break;
    }
}
  • 2.3.8 回不去了 好像分析有误^0^………………..接着看connectionSendInitCfm函数
void connectionSendInitCfm(Task task, connection_lib_status status, cl_dm_bt_version version)
{    
    MAKE_CL_MESSAGE(CL_INIT_CFM);
    message->status = status;//这里这个是fail
    message->version = version;//版本的话没有bluetooth_unknown
    /*这个函数是重点,这个task是最重要的任务,所以会往上面返回一个没有初始化成功的标志*/
    MessageSend(task, CL_INIT_CFM, message);

    /* Cancel initialisation timeout */
    if(status == success)
        (void) MessageCancelFirst(connectionGetCmTask(), CL_INTERNAL_INIT_TIMEOUT_IND);
}

汗啊,分析了一遍结果发现到头来初始化失败了啊!!
莫急,有没有注意到我上面说的那个时间很重要的参数,没错就是在这里,当这个时间到之后,确实会发送初始化失败,但是如果底层初始化成功的话这个消息将被取消,不会发往上面,其实程序看到这里的话,应该知道当状态变为初始化完成时,会向上面发送成功的标志,应该说流程大致有个印象,对于具体的其状态是怎样切换成初始化完成的,这个有待后面研究,目前我也不是很懂。

3.几个需要重要关注的地方

  • 结构体的理解是个重点
  • 连接库初始化的过程中状态的切换是个重点:这个没有解决!!!
    百度文档搜索到一篇不错的文档:
    注:参考文档,百度文档的对sink流程的分析–CSR ADK sink理解

你可能感兴趣的:(csr8670从菜鸟开始)